package com.bf.hotpoint.applepushnotification;

import java.io.File;
import java.io.IOException;
import java.util.*;

import org.apache.log4j.BasicConfigurator;
import org.springframework.stereotype.Service;

import javapns.Push;
import javapns.communication.KeystoreManager;
import javapns.communication.exceptions.*;
import javapns.devices.*;
import javapns.devices.exceptions.InvalidDeviceTokenFormatException;
import javapns.devices.implementations.basic.*;
import javapns.notification.*;
import javapns.notification.transmission.*;
import javapns.org.json.JSONException;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;

@Service
public class PushNotificationImpl implements PushNotification{
	private static PushQueue pushQueue;
	
	static{
		Resource resource = new ClassPathResource("certificate.p12");
		String keystore = null;
		try {
			keystore = resource.getFile().getPath();
		} catch (IOException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		String password = "Ruofeng123!";
		boolean production = false;
		int threadThreads = 10;

		verifyKeystore(keystore, password, production);
		try {
			pushQueue = Push.queue(keystore, password, production, threadThreads);
		} catch (KeystoreException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}

	public void pushNotification(String token,String content,String messageId){
		try {
			PushNotificationPayload payload = PushNotificationPayload.alert(content);
			try {
				payload.addCustomDictionary("messageId", messageId);
			} catch (JSONException e) {
				e.printStackTrace();
			}
			pushQueue.start();
			pushQueue.add(payload,token);
		} catch (InvalidDeviceTokenFormatException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Execute this class from the command line to run tests.
	 * 
	 * @param args
	 */
	public static void main(String[] args) {
		
		/* Verify that the test is being invoked  */
		if (!verifyCorrectUsage(PushNotificationImpl.class, args, "keystore-path", "keystore-password", "device-token", "[production|sandbox]", "[complex|simple|threads]", "[#devices]", "[#threads]")) return;

		/* Initialize Log4j to print logs to console */
		configureBasicLogging();

		/* Push an alert */
		try {
			pushTest();
		} catch (CommunicationException e) {
			e.printStackTrace();
		} catch (KeystoreException e) {
			e.printStackTrace();
		}
		try {
			Thread.sleep(10000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	private PushNotificationImpl() {
	}


	/**
	 * Push a test notification to a device, given command-line parameters.
	 * 
	 * @param args
	 * @throws KeystoreException 
	 * @throws CommunicationException 
	 */
	private static void pushTest() throws CommunicationException, KeystoreException {
		Resource resource = new ClassPathResource("certificate.p12");
		String keystore = null;
		try {
			keystore = resource.getFile().getPath();
		} catch (IOException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		String password = "Ruofeng123!";
		String token = "205f8c84a877adcfce0824c72b10f3150fa995fb40ead74a695fd07ba39e88ad";
		boolean production = false;
		boolean simulation = false;
		int threadDevices = 3;
		int threadThreads = 10;

		verifyKeystore(keystore, password, production);

		/* Push a test alert 
		List<PushedNotification> notifications = Push.test(keystore, password, production, token);
		printPushedNotifications(notifications);
		*/
		
		PushQueue pq = Push.queue(keystore, password, production, 3);
		pq.start();
		try {
			PushNotificationPayload payload = PushNotificationPayload.alert("Hello World!");
			pq.add(payload,token);
			System.out.println("CriticalExceptions size"+pq.getCriticalExceptions().size());
			System.out.println("notifications size"+pq.getPushedNotifications().size());
			payload = PushNotificationPayload.alert("Hello World!2");
			pq.add(payload,token);
			payload = PushNotificationPayload.alert("Hello World!3");
			pq.add(payload,token);
			payload = PushNotificationPayload.alert("Hello World!4");
			pq.add(payload,token);
			payload = PushNotificationPayload.alert("Hello World!5");
			pq.add(payload,token);
			payload = PushNotificationPayload.alert("Hello World!6");
			pq.add(payload,token);
		} catch (InvalidDeviceTokenFormatException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println("CriticalExceptions size"+pq.getCriticalExceptions().size());
		System.out.println("notifications size"+pq.getPushedNotifications().size());
		
		/* Push a more complex payload 
		List<PushedNotification> notifications = Push.payload(createComplexPayload(), keystore, password, production, token);
		printPushedNotifications(notifications);
		*/
		
			/* Push a Hello World! alert repetitively using NotificationThreads 
			pushSimplePayloadUsingThreads(keystore, password, production, token, simulation, threadDevices, threadThreads);
			*/
	}


	/**
	 * Create a complex payload for test purposes.
	 * @return
	 */
	@SuppressWarnings("unchecked")
	private static Payload createComplexPayload() {
		PushNotificationPayload complexPayload = PushNotificationPayload.complex();
		try {
			// You can use addBody to add simple message, but we'll use
			// a more complex alert message so let's comment it
			complexPayload.addCustomAlertBody("My alert message");
			complexPayload.addCustomAlertActionLocKey("Open App");
			complexPayload.addCustomAlertLocKey("javapns rocks %@ %@%@");
			ArrayList parameters = new ArrayList();
			parameters.add("Test1");
			parameters.add("Test");
			parameters.add(2);
			complexPayload.addCustomAlertLocArgs(parameters);
			complexPayload.addBadge(45);
			complexPayload.addSound("default");
			complexPayload.addCustomDictionary("acme", "foo");
			complexPayload.addCustomDictionary("acme2", 42);
			ArrayList values = new ArrayList();
			values.add("value1");
			values.add(2);
			complexPayload.addCustomDictionary("acme3", values);
		} catch (JSONException e) {
			System.out.println("Error creating complex payload:");
			e.printStackTrace();
		}
		return complexPayload;
	}


	protected static void pushSimplePayloadUsingThreads(String keystore, String password, boolean production, String token, boolean simulation, int devices, int threads) {
		try {

			System.out.println("Creating PushNotificationManager and AppleNotificationServer");
			AppleNotificationServer server = new AppleNotificationServerBasicImpl(keystore, password, production);
			System.out.println("Creating payload (simulation mode)");
			//			Payload payload = PushNotificationPayload.alert("Hello World!");
			Payload payload = PushNotificationPayload.test();

			System.out.println("Generating " + devices + " fake devices");
			List<Device> deviceList = new ArrayList<Device>(devices);
			for (int i = 0; i < devices; i++) {
				String tokenToUse = token;
				if (tokenToUse == null || tokenToUse.length() != 64) {
					tokenToUse = "123456789012345678901234567890123456789012345678901234567" + (1000000 + i);
				}
				deviceList.add(new BasicDevice(tokenToUse));
			}

			System.out.println("Creating " + threads + " notification threads");
			NotificationThreads work = new NotificationThreads(server, simulation ? payload.asSimulationOnly() : payload, deviceList, threads);
			//work.setMaxNotificationsPerConnection(10000);
			System.out.println("Linking notification work debugging listener");
			work.setListener(DEBUGGING_PROGRESS_LISTENER);

			System.out.println("Starting all threads...");
			long timestamp1 = System.currentTimeMillis();
			work.start();
			System.out.println("All threads started, waiting for them...");
			work.waitForAllThreads();
			long timestamp2 = System.currentTimeMillis();
			System.out.println("All threads finished in " + (timestamp2 - timestamp1) + " milliseconds");

			printPushedNotifications(work.getPushedNotifications());

		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * A NotificationProgressListener you can use to debug NotificationThreads.
	 */
	public static final NotificationProgressListener DEBUGGING_PROGRESS_LISTENER = new NotificationProgressListener() {

		public void eventThreadStarted(NotificationThread notificationThread) {
			System.out.println("   [EVENT]: thread #" + notificationThread.getThreadNumber() + " started with " + notificationThread.getDevices().size() + " devices beginning at message id #" + notificationThread.getFirstMessageIdentifier());
		}


		public void eventThreadFinished(NotificationThread thread) {
			System.out.println("   [EVENT]: thread #" + thread.getThreadNumber() + " finished: pushed messages #" + thread.getFirstMessageIdentifier() + " to " + thread.getLastMessageIdentifier() + " toward " + thread.getDevices().size() + " devices");
		}


		public void eventConnectionRestarted(NotificationThread thread) {
			System.out.println("   [EVENT]: connection restarted in thread #" + thread.getThreadNumber() + " because it reached " + thread.getMaxNotificationsPerConnection() + " notifications per connection");
		}


		public void eventAllThreadsStarted(NotificationThreads notificationThreads) {
			System.out.println("   [EVENT]: all threads started: " + notificationThreads.getThreads().size());
		}


		public void eventAllThreadsFinished(NotificationThreads notificationThreads) {
			System.out.println("   [EVENT]: all threads finished: " + notificationThreads.getThreads().size());
		}


		public void eventCriticalException(NotificationThread notificationThread, Exception exception) {
			System.out.println("   [EVENT]: critical exception occurred: " + exception);
		}
	};


	/**
	 * Print to the console a comprehensive report of all pushed notifications and results.
	 * @param notifications a raw list of pushed notifications
	 */
	public static void printPushedNotifications(List<PushedNotification> notifications) {
		List<PushedNotification> failedNotifications = PushedNotification.findFailedNotifications(notifications);
		List<PushedNotification> successfulNotifications = PushedNotification.findSuccessfulNotifications(notifications);
		int failed = failedNotifications.size();
		int successful = successfulNotifications.size();

		if (successful > 0 && failed == 0) {
			printPushedNotifications("All notifications pushed successfully (" + successfulNotifications.size() + "):", successfulNotifications);
		} else if (successful == 0 && failed > 0) {
			printPushedNotifications("All notifications failed (" + failedNotifications.size() + "):", failedNotifications);
		} else if (successful == 0 && failed == 0) {
			System.out.println("No notifications could be sent, probably because of a critical error");
		} else {
			printPushedNotifications("Some notifications failed (" + failedNotifications.size() + "):", failedNotifications);
			printPushedNotifications("Others succeeded (" + successfulNotifications.size() + "):", successfulNotifications);
		}
	}


	/**
	 * Print to the console a list of pushed notifications.
	 * @param description a title for this list of notifications
	 * @param notifications a list of pushed notifications to print
	 */
	public static void printPushedNotifications(String description, List<PushedNotification> notifications) {
		System.out.println(description);
		for (PushedNotification notification : notifications) {
			try {
				System.out.println("  " + notification.toString());
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	
	
	
	
	
	
	
	
	/**-----------------foundation-------------------*/

	static boolean verifyCorrectUsage(Class testClass, String[] argsProvided, String... argsRequired) {
		if (argsProvided == null) return true;
		int numberOfArgsRequired = countArgumentsRequired(argsRequired);
		if (argsProvided.length < numberOfArgsRequired) {
			String message = getUsageMessage(testClass, argsRequired);
			System.out.println(message);
			return false;
		}
		return true;
	}


	private static String getUsageMessage(Class testClass, String... argsRequired) {
		StringBuilder message = new StringBuilder("Usage: ");
		message.append("java -cp \"<required libraries>\" ");
		message.append(testClass.getName());
		for (String argRequired : argsRequired) {
			boolean optional = argRequired.startsWith("[");
			if (optional) {
				message.append(" [");
				message.append(argRequired.substring(1, argRequired.length() - 1));
				message.append("]");
			} else {
				message.append(" <");
				message.append(argRequired);
				message.append(">");
			}
		}
		return message.toString();
	}


	private static int countArgumentsRequired(String... argsRequired) {
		int numberOfArgsRequired = 0;
		for (String argRequired : argsRequired) {
			if (argRequired.startsWith("[")) break;
			numberOfArgsRequired++;
		}
		return numberOfArgsRequired;
	}


	/**
	 * Enable Log4J with a basic default configuration (console only).
	 */
	public static void configureBasicLogging() {
		try {
			BasicConfigurator.configure();
		} catch (Exception e) {
		}
	}


	/**
	 * Validate a keystore reference and print the results to the console.
	 * 
	 * @param keystoreReference a reference to or an actual keystore
	 * @param password password for the keystore
	 * @param production service to use
	 */
	public static void verifyKeystore(Object keystoreReference, String password, boolean production) {
		try {
			System.out.print("Validating keystore reference: ");
			KeystoreManager.validateKeystoreParameter(keystoreReference);
			System.out.println("VALID  (keystore was found)");
		} catch (Exception e) {
			e.printStackTrace();
		}
		if (password != null) {
			try {
				System.out.print("Verifying keystore content: ");
				AppleNotificationServer server = new AppleNotificationServerBasicImpl(keystoreReference, password, production);
				KeystoreManager.verifyKeystoreContent(server, keystoreReference);
				System.out.println("VERIFIED  (no common mistakes detected)");
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

}
